﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using Cofe.Core;
using Cofe.Core.EntryType;
using Cofe.Core.Implements;
using Cofe.Core.Property;

namespace Cofe.Web.Utils
{
    public static class UrlUtils
    {
        //http://stackoverflow.com/questions/829080/how-to-build-a-query-string-for-a-url-in-c
        public static string ToQueryString(this NameValueCollection nvc, Func<string, bool> keyFilter = null)
        {
            if (keyFilter == null)
                keyFilter = (key) => true;
            var array = (from key in nvc.AllKeys
                         where keyFilter(key)
                         from value in nvc.GetValues(key)
                         select string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value)))
                .ToArray();
            if (array.Length == 0)
                return "";
            return "?" + string.Join("&", array);
        }

        public static string AbsoluteUriWithoutQueryString(this Uri uri)
        {
            return uri.AbsoluteUri.Replace(uri.Query, "").TrimEnd('?');
        }

        public static Uri BaseAddress
        {
            get
            {
                if (System.Web.HttpContext.Current == null)
                    return new Uri("http://localhost", UriKind.Absolute);
                string scheme = System.Web.HttpContext.Current.Request.Url.Scheme;
                string host = System.Web.HttpContext.Current.Request.Url.Host;
                int port = System.Web.HttpContext.Current.Request.Url.Port;

                if (String.IsNullOrEmpty(scheme)) throw new ArgumentNullException("Scheme");
                if (String.IsNullOrEmpty(host)) throw new ArgumentNullException("Host");

                return new Uri(String.Format("{0}://{1}:{2}", scheme, host, port));
            }
        }

        public static Uri FormatUri(string relPath)
        {
            if (String.IsNullOrEmpty(relPath))
                relPath = "";
            relPath = relPath.TrimStart('/');
            
            return new Uri(String.Format("{0}/{1}", BaseAddress.AbsoluteUri.TrimEnd('/'), relPath));
        }

        public static IEnumerable<XmlLink> CreateLinks
            (IPropertyHost propertyHost, IEnumerable<object> properties = null,
            bool includeDetails = true)
        {
            return CreateLinks<CofeSystemInfo>(propertyHost, properties, includeDetails);
        }

        public static IEnumerable<XmlLink> CreateLinks
            <T>(IPropertyHost propertyHost, IEnumerable<object> properties = null,
            bool includeDetails = true) where T : CofeSystemInfo
        {
            var entryId = propertyHost.Behaviors.GetProperty<Guid>(CacheProperties.Id);
            var parentId = propertyHost.Behaviors.GetProperty<Guid>(CacheProperties.ParentId);
            var entryTypeInfo = propertyHost.Behaviors.GetProperty<IEntryTypeInfo>(CofeEntryTypeProperties.EntryTypeInfo);
            Guid entryTypeId = entryTypeInfo == null ? Guid.Empty : entryTypeInfo.Id;

            if (properties == null)
                properties = propertyHost.Behaviors.GetSupportedProperties();

            Func<RelationType, string, Uri> uriFunc = (rel, res) =>
                {
                    switch (rel)
                    {
                        case RelationType.Self :
                            return FormatUri(CofeWsServices.EntryApiPath + "/" + entryId + (res != null ? "/" + res : ""));
                        case RelationType.TypeInfo :
                            return entryTypeId == Guid.Empty ? null : 
                                FormatUri(CofeWsServices.EntryApiPath + "/" + entryTypeId + (res != null ? "/" + res : ""));
                        case RelationType.Parent :
                            return FormatUri(CofeWsServices.EntryApiPath + "/" + parentId + (res != null ? "/" + res : ""));
                        default:
                            throw new NotSupportedException();
                    }
                };

            if (entryId == Guid.Empty)
            {
                string entryParseName = propertyHost.Behaviors.GetProperty<string>(CofeProperties.FullParseName);
                if (!(String.IsNullOrEmpty(entryParseName))) //Not cached entry.
                {
                    string parentParseName = PathFE.GetDirectoryName(entryParseName);
                    string entryTypeParseName = entryTypeInfo != null ? entryTypeInfo.ParseName : null;
                    uriFunc = (rel, res) =>
                    {
                        switch (rel)
                        {
                            case RelationType.Self:
                                return FormatUri(CofeWsServices.ParseApiPath + "?path=" + entryParseName + (res != null ? "&rel=" + res : ""));
                            case RelationType.TypeInfo:                                
                                return entryTypeParseName == null ? null : 
                                    FormatUri(CofeWsServices.ParseApiPath + "?path=" + entryTypeParseName);
                            case RelationType.Parent:
                                return parentParseName == null ? null : 
                                FormatUri(CofeWsServices.ParseApiPath + "?path=" + parentParseName + (res != null ? "&rel=" + res : ""));
                            default:
                                throw new NotSupportedException();
                        }
                    };

                }
            }


            return CreateLinks<T>(uriFunc, propertyHost.Behaviors.GetSupportedProperties().ToArray(), includeDetails);
        }


        public static IEnumerable<XmlLink> CreateParameterDicLinks(ParameterDic pd)
        {
            if (pd == null)
                yield break;

            if (pd.ContainsKey(ParameterKeys.CurrentUri))
            {
                Uri currentUri = (Uri)pd[ParameterKeys.CurrentUri];
                yield return new XmlLink(MediaType.EntryList, RelationType.Self, currentUri);

                if (pd.ContainsKey(ParameterKeys.CurrentPage) &&
                    pd.ContainsKey(ParameterKeys.TotalPages) &&
                    pd.ContainsKey(ParameterKeys.ItemsPerPage))
                {
                    //Paginate
                    string currentUriWithoutPaginate =
                        HttpContext.Current.Request.Url.AbsoluteUriWithoutQueryString() +
                        HttpContext.Current.Request.QueryString.ToQueryString(
                            key => !key.Equals("page", StringComparison.InvariantCultureIgnoreCase));

                    string currentUriMask = currentUriWithoutPaginate + (currentUriWithoutPaginate
                        .IndexOf('?') == -1 ? "?" : "&") + "page={0},{1}";

                    int currentPage = (int)pd[ParameterKeys.CurrentPage];
                    int totalPages = (int)pd[ParameterKeys.TotalPages];
                    int itemPerPage = (int)pd[ParameterKeys.ItemsPerPage];


                    if (currentPage > 1)
                        yield return new XmlLink(MediaType.EntryList, RelationType.Prev,
                            new Uri(string.Format(currentUriMask, currentPage - 1, itemPerPage)));

                    if (currentPage < totalPages)
                        yield return new XmlLink(MediaType.EntryList, RelationType.Next,
                            new Uri(string.Format(currentUriMask, currentPage + 1, itemPerPage)));
                }
            }
        }

        public static IEnumerable<XmlLink> CreateLinks<T>(
            Func<RelationType, string, Uri> uriFunc,
            IEnumerable<object> properties,
            bool includeDetails = true) where T : CofeSystemInfo
        {
            string entryApiPath = CofeWsServices.EntryApiPath;
            var resourceProps = properties.Where(p => DecoratorExtension.IsWebResource(p) || DecoratorExtension.IsWebAction(p))
                 .ToDictionary(p => p, p => CofeServices.PropertyDictionary.AttributeHelper.FindAttribute<WebResourceAttribute>(p));

            var linkProps = resourceProps
                .Where(pp => includeDetails || pp.Value.IsMajorProperty<T>());

            foreach (var ppair in linkProps)
            {
                string propName = CofeServices.PropertyDictionary.GetPropertyString(ppair.Key);
                Uri uri = uriFunc(RelationType.Self, propName);
                if (ppair.Value.RelationType == RelationType.Property)
                    yield return new XmlLink(ppair.Value.MediaType, propName, uri, ppair.Key);
                else yield return new XmlLink(ppair.Value.MediaType, ppair.Value.RelationType, uri, ppair.Key);
            }

            yield return new XmlLink(MediaType.Entry, RelationType.Self,
                uriFunc(RelationType.Self, null), null);

            if (uriFunc(RelationType.TypeInfo, null) != null)
                yield return new XmlLink(MediaType.Entry, RelationType.TypeInfo,
                    uriFunc(RelationType.TypeInfo, null), null);

            if (includeDetails && uriFunc(RelationType.Parent, null) != null)
                yield return new XmlLink(MediaType.Entry, RelationType.Parent,
                    uriFunc(RelationType.Parent, null), null);
        }
    }
}
